﻿#region Copyright RenGuiYou. All rights reserved.

//=====================================================
// NeatlyFrameWork
// Author:      RenGuiyou
// Feedback: 	mailto:750539605@qq.com
//=====================================================

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Neatly.Load;
using Neatly.Load.Manager;
using Neatly.Load.Pool;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Neatly.Module
{
    public class LoadModule : ModuleSingleton<LoadModule>
    {
        #region property

        AssetBundleManifest m_Manifest;

        #endregion

        #region Pool

        //loader bundle pool
        private readonly LoaderPool m_LoaderPool = new();
        private readonly HashSet<string> m_AsyncLoaderSet = new();

        #endregion

        #region subitem manager

        private readonly AssetBundleMapManager m_AssetBundleMapManager = Singleton<AssetBundleMapManager>.Instance;
        private readonly AtlasLoadManager m_AtlasLoadManager = Singleton<AtlasLoadManager>.Instance;
        private readonly LuaLoadManager m_LuaLoadManager = Singleton<LuaLoadManager>.Instance;
        private readonly SoundLoadManager m_SoundLoadManager = Singleton<SoundLoadManager>.Instance;
        private readonly VideoLoadManager m_VideoLoadManager = Singleton<VideoLoadManager>.Instance;
        private readonly SceneLoadManager m_SceneLoadManager = Singleton<SceneLoadManager>.Instance;

        #endregion

        public override async UniTask Init()
        {
            m_VideoLoadManager.Init(this);
            m_SceneLoadManager.Init(this);
            m_AtlasLoadManager.Init(this);
            List<UniTask> taskList = new List<UniTask>();
            if (NeatlyConfig.AssetBundleEnable)
            {
                taskList.Add(LoadManifest());
            }

            taskList.Add(m_AssetBundleMapManager.Init(this));
            await UniTask.WhenAll(taskList);
            NeatlyFrame.Instance.RegisterDestroyAction(m_LoaderPool.LoseRef);
        }

        //热加载
        public async UniTask HotReload()
        {
            List<UniTask> taskList = new List<UniTask>();
#if !UNITY_WEBGL
            if (NeatlyConfig.AssetBundleEnable)
            {
                taskList.Add(LoadManifest());
            }

            taskList.Add(m_AssetBundleMapManager.Init(this));
#endif
            taskList.Add(m_SoundLoadManager.Init(this));
            taskList.Add(m_LuaLoadManager.Init(this));
            await UniTask.WhenAll(taskList);
        }

        public void ClearVersion()
        {
            m_LoaderPool.ClearVersion();
        }

        public async UniTask<T> LoadAsset<T>(string path, bool persistent = false) where T : Object
        {
            var (_, asset) = await LoadAssetLoader<T>(path, persistent);
            return asset;
        }
        // public void LoadAssetNoAddRef<T>(string path, Action<Object> callback,  bool persistent = false) where T : Object
        // {
        //     LoadAssetLoader(path, typeof(T), data =>
        //     {
        //         if (data == null)
        //         {
        //             NDebug.LogError("材质加载失败: path:{0}", path);
        //             return;
        //         }
        //         T source = data as T;
        //         callback(source);
        //     }, persistent, true);
        // }

        //加载图集
        public async UniTask<NAtlas> LoadAtlasInfo(string atlasName)
        {
            Loader loader = m_LoaderPool.GetAtlasLoader(atlasName);
            if (loader != null)
            {
                loader.AddReferenceCount();
                return loader.AtlasInfo;
            }

            string path = $"{NeatlyConfig.PATH_ATLAS_TP}{atlasName}";
            var (atlasLoader, _) = await LoadAssetLoader<GameObject>(path);
            atlasLoader.AddReferenceCount();
            return atlasLoader.AtlasInfo;
        }

        public void AddAtlasRef(string atlasName)
        {
            m_LoaderPool.AddAtlasRef(atlasName);
        }

        //解除Atlas引用
        public void LoseAtlasRef(string atlasName)
        {
            m_LoaderPool.LoseAtlasRef(atlasName);
        }

        public async UniTask<(Loader, T)> LoadAssetLoader<T>(string path, bool persistent = false, bool notAddRef = false) where T : Object
        {
            if (NeatlyConfig.AssetBundleEnable)
            {
                return await LoadAssetFromBundle<T>(path, persistent, notAddRef);
            }

            path = $"{path}{GetSuffix(typeof(T))}";
            return await LoadAssetFile<T>(path);
        }

        #region LoadAssetBundle

        //加载Manifest
        private async UniTask LoadManifest()
        {
            var loader = await Loader.AutoLoad<AssetBundleLoader>(NeatlyConfig.NAME_MANIFEST);
            AssetBundle resultObject = loader.ResultObject as AssetBundle;
            if (resultObject == null)
            {
                Debug.LogError($"[Manifest错误] Path:{NeatlyConfig.NAME_MANIFEST}");
                return;
            }

            m_Manifest = resultObject.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            NeatlyConfig.AssetBundleUnloadAction(resultObject, false);
        }

        //加载AssetBundle
        public async UniTask<Loader> LoadAssetBundle(string path, bool sync = true, bool persistent = false, bool isDependency = false)
        {
            if (path.IndexOf("data_ttf") != -1)
            {
                persistent = true;
            }

            path = AssetBundleMapManager.Instance.Get(path);
            //判定是否在加载队列中
            if (m_AsyncLoaderSet.Contains(path))
            {
                await UniTask.WaitUntil(() => m_LoaderPool.Get(path, false) != null);
                return m_LoaderPool.Get(path, isDependency);
            }

            //缓存有就拿缓存
            Loader result = m_LoaderPool.Get(path, isDependency);
            if (result != null)
            {
                return result;
            }

            //异步
            m_AsyncLoaderSet.Add(path);
            await LoadDependencies(path, sync, persistent);
            var loader = await Loader.AutoLoad<AssetBundleLoader>(path);
            loader.SetPersistent(persistent);
            if (isDependency)
            {
                loader.AddDependCount();
            }

            m_LoaderPool.Add(loader.Path, loader);
            m_AsyncLoaderSet.Remove(path);
            return loader;
        }

        //加载依赖
        private async UniTask LoadDependencies(string name, bool sync, bool persistent)
        {
            string[] dependArray = m_Manifest.GetAllDependencies(name);
            List<UniTask<Loader>> loaderList = new List<UniTask<Loader>>();
            foreach (var path in dependArray)
            {
                var task = LoadAssetBundle(path, sync, persistent, true);
                NeatlyAction.LoadBundleComplete?.Invoke(path);
                loaderList.Add(task);
            }

            await UniTask.WhenAll(loaderList);
        }

        public string[] GetAllLoadBundleName(string path)
        {
            path = AssetBundleMapManager.Instance.Get(path);
            HashSet<string> hashSet = new HashSet<string>();
            if (NeatlyConfig.AssetBundleEnable)
            {
                GetAllLoadBundleName(path, hashSet);
            }
            return hashSet.ToArray();
        }

        private void GetAllLoadBundleName(string path, HashSet<string> hashSet)
        {
            hashSet.Add(path);
            foreach (var dependencyPath in m_Manifest.GetAllDependencies(path))
            {
                GetAllLoadBundleName(dependencyPath, hashSet);
            }
        }

        public async UniTask<(Loader, T)> LoadAssetFromBundle<T>(string path, bool persistent = false, bool notAddRef = false) where T : Object
        {
            var loader = await LoadAssetBundle(path, true, persistent);
            NeatlyAction.LoadBundleComplete?.Invoke(path);
            string name = path.GetFileName();
            var asset = loader.LoadAsset<T>(name);
            if (notAddRef)
            {
                loader.LoseReferenceCount();
            }

            if (asset == null)
            {
                Debug.LogError($"Bundle加载资源错误, path:{path},name:{name}, asset is null");
            }

            return (loader, asset);
        }

        #endregion

        //直接加载AssetBundle
        public async UniTask<Loader> LoadAssetBundleDirect(string path, bool sync = true, bool persistent = false)
        {
            //判定是否在加载队列中
            if (m_AsyncLoaderSet.Contains(path))
            {
                await UniTask.WaitUntil(() => m_LoaderPool.Get(path, false) != null);
                return m_LoaderPool.Get(path, persistent);
            }

            //缓存有就拿缓存
            Loader result = m_LoaderPool.Get(path, persistent);
            if (result != null)
            {
                return result;
            }

            //异步
            m_AsyncLoaderSet.Add(path);
            var loader = await Loader.AutoLoad<AssetBundleLoader>(path);
            loader.SetPersistent(persistent);
            m_LoaderPool.Add(loader.Path, loader);
            m_AsyncLoaderSet.Remove(path);
            return loader;
        }

        //获取指定loader
        public Loader GetLoader(string path)
        {
            Loader loader = m_LoaderPool.Get(path, false);
            return loader;
        }

        #region LoadAssetFile

        public async UniTask<(Loader, T)> LoadAssetFile<T>(string path) where T : Object
        {
            var loader = await Loader.AutoLoad<AssetFileLoader>(path);
            m_LoaderPool.Add(loader.Path, loader);
            return (loader, loader.ResultObject as T);
        }

        public async UniTask<T> LoadAssetFileSync<T>(string path) where T : Object
        {
            var loader = await Loader.AutoLoad<AssetFileLoader>(path);
            return loader.ResultObject as T;
        }

        #endregion

        public override void Update(float dt)
        {
            m_LoaderPool.Update();
        }

        #region 可视化工具接口(业务与框架皆不可使用)

        public System.Collections.Generic.Dictionary<string, Loader> GetAllLoader()
        {
            return m_LoaderPool.GetAllLoader();
        }

        #endregion

        private static string GetSuffix(Type type)
        {
            if (type == typeof(GameObject))
                return ".prefab";
            if (type == typeof(AtlasConfig))
                return ".asset";
            if (type == typeof(Material))
                return ".mat";
            return string.Empty;
        }
    }
}